#!/bin/bash

# 脚本路径和日志路径
cd $(dirname $0)
base_path=$(pwd)
base_name=$(basename $0)
log_path=$base_path/logs/client.log
new_log_path=$base_path/logs/new.log


# 创建文件夹
mkdir -p $base_path/status
mkdir -p $base_path/logs
mkdir -p $base_path/sh
mkdir -p $base_path/cmds
mkdir -p $base_path/lanips


first_param="$1"
if [ "$first_param" = "help" ]; then # 帮助
	echo "Usage: $0 [help|install|uninstall|on-boot|register]"
	echo "help: 显示帮助信息"
	echo "install: 安装，需要指定服务器地址和端口，例如：$0 install 192.168.1.1 80"
	echo "uninstall: 卸载"
	echo "onboot: 系统启动时执行的命令，定时任务执行时需要"
	echo "register: 注册，会将密钥发送给服务器，使得服务器可以远程下发命令给设备"
	exit 0
fi
if [ "$first_param" = "install" ]; then # 安装
	echo 开始安装
	# 添加定时任务
	(crontab -l 2>/dev/null | grep -v "$base_path/$base_name"; echo -e "* * * * * $base_path/$base_name") | crontab -
	if [ $? -eq 0 ]; then
		echo "定时任务添加成功"
	else
		echo "定时任务添加失败"
		exit 1
	fi
	# 添加rc.local
	echo -e "$(echo "$base_path/$base_name onboot &")\n$(cat /etc/rc.local 2>/dev/null | grep -v "$base_path/$base_name")" > /etc/rc.local
	if [ $? -eq 0 ]; then
		echo "rc.local添加成功"
		chmod +x /etc/rc.local
	else
		echo "rc.local添加失败，请在 /etc/rc.local 中手动添加：$base_path/$base_name onboot"
	fi
	# 重启cron服务
	/etc/init.d/cron restart
	if [ $? -eq 0 ]; then
		echo "cron服务重启成功"
	else
		echo "cron服务重启失败"
		exit 1
	fi
	# 设置服务器IP和端口
	server_ip="$2"
	server_port="$3"
	if [ -n "$server_ip" ]; then
		echo $server_ip > $base_path/status/server_ip
	fi
	if [ -n "$server_port" ]; then
		echo $server_port > $base_path/status/server_port
	fi
	# 设置为启用
	echo 1 > $base_path/status/enable
	echo "安装完成"
	exit 0
fi
if [ "$first_param" = "uninstall" ]; then # 卸载
	echo 开始卸载
	# 设置为不启用
	echo 正在停止程序...
	echo 0 > $base_path/status/enable
	# 删除定时任务
	crontab -l 2>/dev/null | grep -v "$base_path/$base_name" | crontab -
	if [ $? -eq 0 ]; then
		echo "定时任务删除成功"
	else
		echo "定时任务删除失败"
		exit 1
	fi
	# 重启cron服务
	/etc/init.d/cron restart
	if [ $? -eq 0 ]; then
		echo "cron服务重启成功"
	else
		echo "cron服务重启失败"
		exit 1
	fi
	echo "卸载完成"
	exit 0
fi
if [ "$first_param" = "onboot" ]; then # 系统启动时执行的命令
	echo 1 > $base_path/status/booted
	LOG_TIME=$(date +"%Y-%m-%d %H:%M:%S")
	echo "[$LOG_TIME] 系统启动" >> $log_path
	echo "[$LOG_TIME] 系统启动" >> $new_log_path
	# exit 0 # 不需要退出，继续执行更好些，因为有时候定时任务执行时机会比较慢
fi
if [ "$first_param" = "register" ]; then # 注册
	ignore_process=1 # 忽略进程检测，因为注册需要依赖与服务器的交互
fi


# 获取脚本开始执行的时间（秒）
start_time=$(date +%s)




# 初始化参数
secret=$(cat $base_path/status/secret)
server_ip=$(cat $base_path/status/server_ip)
server_port=$(cat $base_path/status/server_port)
client_id=$(cat $base_path/status/client_id)

# 检查是否设置了服务器IP
if [ -z "$server_ip" ]; then
  echo "未设置服务器地址，程序退出"
  exit 1
fi
if [ -z "$server_port" ]; then
  server_port=80
fi

# 检查是否有client_id的值
if [ -z "$client_id" ]; then
  # 生成一个随机值（例如，一个 16 位的随机数）
  random_value=$(< /dev/urandom tr -dc 'A-Za-z0-9' | head -c16)
  client_id=${random_value}
  echo $client_id > $base_path/status/client_id
  echo "未设置client_id，自动生成随机id：$client_id"
fi

# 检测是否有密钥
if [ -z "$secret" ]; then
	random_value=$(< /dev/urandom tr -dc 'A-Za-z0-9' | head -c16)
	secret=${random_value}
	echo $secret > $base_path/status/secret
	echo "未设置密钥，自动生成随机密钥：$secret"
fi

# 定义一个函数对每个局域网IP进行在线检测
process_lanip() {
    local id="$1"
    local ip="$2"

    # 对IP地址进行两次ping操作
    if ping -c 1 -W 1 "$ip" > /dev/null 2>&1 || ping -c 1 -W 1 "$ip" > /dev/null 2>&1; then
		temp=$(cat $base_path/lanips/$id)
		if [ "$temp" != "1" ]; then
			LOG_TIME=$(date +"%Y-%m-%d %H:%M:%S")
			echo "[$LOG_TIME] 局域网地址 $ip 上线" >> $log_path
			echo "[$LOG_TIME] 局域网地址 $ip 上线" >> $new_log_path
		fi
        echo "1" > "$base_path/lanips/$id"
    else
		temp=$(cat $base_path/lanips/$id)
		if [ "$temp" != "0" ]; then
			LOG_TIME=$(date +"%Y-%m-%d %H:%M:%S")
			echo "[$LOG_TIME] 局域网地址 $ip 离线" >> $log_path
			echo "[$LOG_TIME] 局域网地址 $ip 离线" >> $new_log_path
		fi
        echo "0" > "$base_path/lanips/$id"
    fi
}


authed=0
while true; do #for j in {1..11}; do # 多次循环，大概5秒1次，考虑时间损耗，最多循环11次
	# 判断是否需要执行
	enable=$(cat $base_path/status/enable)
	if [ "$enable" != 1 ]; then
	echo "未启用，程序退出"
	exit 0 #说明不需要执行
	fi


	# 判断是否有其他脚本进程存在，如果有则退出执行
	# 设置一个last_check文件，记录最后一次检测的时间，用于判断是否有脚本在持续运行，防止脚本重复运行
	if [ "$ignore_process" != 1 ]; then
		last_check_time=$(cat $base_path/status/last_check)
		current_time=$(date +%s)
		if [ -z "$last_check_time" ]; then
			echo -n "$current_time" > $base_path/status/last_check
		else
			if [ "$((current_time - last_check_time))" -ge 0 ] && [ "$((current_time - last_check_time))" -lt 5 ]; then #两个判断条件，防止重启后时间错误出现负数导致脚本退出
				echo "间隔时间小于5秒，检测到重复脚本进程，程序退出"
				exit 0
			else
				echo -n "$current_time" > $base_path/status/last_check
			fi
		fi
	fi


	# 检测局域网设备在线情况，每一次都在后台循环都检测一次
	lanips=$(cat $base_path/status/lanips)
	if [ -n "$lanips" ]; then
		while IFS=',' read -r id ip; do
			if [ "$ip" != "" ]; then
				process_lanip "$id" "$ip" &
			fi
		done < $base_path/status/lanips
	fi

	# 初始化在线设备ID拼接字符串
	online_ids=""
	# 遍历lanips文件中的每一行检查在线状态
	while IFS=',' read -r id ip; do
		if [ -f "$base_path/lanips/$id" ] && [ "$(cat $base_path/lanips/$id)" == "1" ]; then
			if [ -z "$online_ids" ]; then
				online_ids="$id"
			else
				online_ids="$online_ids-$id"
			fi
		fi
	done < $base_path/status/lanips
	echo "在线设备ID: $online_ids"


	# 检测是否断线，如果online不为1，则进行连接检测。如果不在线，则退出。 如果在线，则继续后面的操作
	online_=$(cat $base_path/status/online)
	online=0
	for i in {1..2}; do
		# 使用 -c 1 参数来指定只发送一个 ICMP ECHO 请求包，-W 参数设置超时时间为 X 秒（根据需要调整，这里设为 2 秒作为示例）
		ping -c 1 -W 5 "$server_ip" >/dev/null 2>&1
		if [ $? -eq 0 ]; then
			if [ "$online_" = 0 ]; then
				# 原来为0，现在连上了，说明网络恢复，写恢复日志
				LOG_TIME=$(date +"%Y-%m-%d %H:%M:%S")
				echo "[$LOG_TIME] 网络恢复" >> $log_path
				echo "[$LOG_TIME] 网络恢复" >> $new_log_path
			fi
			online=1
			echo $online > $base_path/status/online
			break
		fi
	done
	if [ "$online" != 1 ]; then #如果检测不在线
		if [ "$online_" = 1 ]; then
			# 原来为1，现在断了，说明网络中断，写断网日志
			LOG_TIME=$(date +"%Y-%m-%d %H:%M:%S")
			echo "[$LOG_TIME] 网络中断" >> $log_path
			echo "[$LOG_TIME] 网络中断" >> $new_log_path
		fi
		online=0
		echo $online > $base_path/status/online
		continue #继续下一次循环，不退出
	fi


	# 处理任务
	cmd_id=0
	msg=""
	first_check=1
	for i in {1..5}; do #最多处理5个任务，避免出现死循环时占用系统资源过多
		while [ "$cmd_id" = 0 ]; do # 这里用while，只是为了方便罗列多个任务，避免嵌套过深
			# 命令id为负数，说明是客户端主动发起的任务
			first_param=$1
			if [ "$first_param" = "register" ]; then
				cmd_id="-3"
				msg=$(echo -n "$secret")
				break
			fi
			if [ "$first_check" = "1" ]; then #第一次检查，无妨先检查是否要更新局域网地址
				lanips_md5=$(cat $base_path/status/lanips_md5)
				cmd_id="-4"
				msg=$(echo -n "${lanips_md5}_${online_ids}")
				break
			fi
			booted=$(cat $base_path/status/booted 2>/dev/null)
			if [ "$booted" = 1 ]; then
				cmd_id="-2"
				break
			fi
			msg=$(cat $base_path/logs/new.log 2>/dev/null)
			if [ -n "$msg" ]; then
				cmd_id="-1"
				break
			fi
			break
		done


		# 命令id为0，说明没有任务要处理
		if [ "$first_check" = 1 ]; then
			first_check=0 #第一次无论什么情况都发送上线信息，不退出
		else
			if [ "$cmd_id" = 0 ]; then # 如果没有任务，且不是第一次，则退出
				break
			fi
		fi


		# 命令id为正数，说明是服务器主动发起的任务，需要执行指令。注意只有认证通过了才能执行指令，避免恶意执行指令
		if [ "$authed" = 1 ]; then
			if [ "$cmd_id" -gt 0 ]; then # 命令id大于0的是要执行的指令
				# current_cmd_id=$(cat $base_path/status/cmd_id)
				# if [ $cmd_id = $current_cmd_id ]; then
				# 	exit 0; #说明此命令正在执行
				# fi
				# echo $cmd_id > $base_path/status/cmd_id
				LOG_TIME=$(date +"%Y-%m-%d %H:%M:%S")
				echo "[$LOG_TIME] 执行命令$cmd_id: $cmd_decoded" >> $log_path
				echo "[$LOG_TIME] 执行命令$cmd_id: $cmd_decoded" >> $new_log_path
				old_result=$(cat $base_path/cmds/$cmd_id 2>/dev/null)
				if [ -n "$old_result" ]; then #对命令结果进行保存处理，是为了防止一条命令执行多次
					echo "命令号 $cmd_id 已经被执行过，不再重复执行"
					msg=$old_result
				else
					echo "$cmd_decoded 已经开始执行，但未得到或不能得到返回结果" > $base_path/cmds/$cmd_id
					msg=$(eval $cmd_decoded 2>&1) #执行指令
					echo $msg > $base_path/cmds/$cmd_id
				fi
				# echo 0 > $base_path/status/cmd_id
			fi
		fi
		
		# 如果$msg大于get请求最大允许的长度的话，则进行截取。这里假定最大长度为2000。必须先截取再base64，否则在中文情况下可能会导致解码错误
		if [ ${#msg} -gt 4000 ]; then
			msg=$(echo "$msg" | head -c 4000)
		fi
		msg=$(echo -n -e "$msg" | openssl base64 | tr -d '\n') #将结果base64编码，并去掉换行符。注意base64后长度会变长，所以需要判断是否超过最大长度

		# 向服务器发送请求
		if command -v curl &> /dev/null; then
			response=$(curl -s "http://$server_ip:$server_port/test?client_id=$client_id&cmd_id=$cmd_id&msg=$msg")
		else
			response=$(wget -q -O- "http://$server_ip:$server_port/test?client_id=$client_id&cmd_id=$cmd_id&msg=$msg")
		fi
		echo "http://$server_ip:$server_port/test?client_id=$client_id&cmd_id=$cmd_id&msg=$msg"
		echo "返回结果："
		echo $response
		# IFS=',' read -r response_status response_message_base64 cmd_id_response cmd_base64 <<< "$response"
IFS=',' read -r response_status response_message_base64 cmd_id_response cmd_base64 check << EOF
$response
EOF
		if [ "$response_status" != 1 ]; then
			continue #说明请求失败
		fi
		response_message=$(echo "$response_message_base64" | openssl base64 -d)
		cmd_decoded=$(echo "$cmd_base64" | openssl base64 -d)
		check2=$(echo -n "$cmd_decoded$secret" | openssl dgst -md5 | awk '{print $2}')
		if [ "$check" != "$check2" ]; then
			authed=0
		else
			authed=1
		fi
		
		
		# 处理客户端主动发起的任务的返回结果
		if [ "$cmd_id" = "-3" ]; then
			echo "注册成功"
			LOG_TIME=$(date +"%Y-%m-%d %H:%M:%S")
			echo "[$LOG_TIME] 注册成功" >> $log_path
			echo "[$LOG_TIME] 注册成功" >> $new_log_path
			exit 0 #注册成功后退出
		elif [ "$cmd_id" = "-4" ]; then
			# 检测respose_message是否有内容，如果有则更新lanips
			if [ -n "$response_message" ]; then
				lanips_md5=$(echo -n "$response_message" | openssl dgst -md5 | awk '{print $2}')
				echo $lanips_md5 > $base_path/status/lanips_md5
				if [ "$response_message" == "0" ]; then
					rm $base_path/status/lanips
				else
					echo "$response_message" > $base_path/status/lanips # 保留最后的回车符，否则会导致最后一条记录无法识别
				fi
				echo "更新lanips成功"
			fi
		elif [ "$cmd_id" = "-2" ]; then
			# 检测是否为刚刚重启，如果是则发送上线信息，cmd_id为2，并同步时间
			booted=0
			echo $booted > $base_path/status/booted
			date -s "$response_message" # 同步时间
		elif [ "$cmd_id" = "-1" ]; then
			# 检查是否有最新的日志信息，如果有则以base64形式发送给服务器，并删除最新日志
			rm $base_path/logs/new.log
		fi
		
		
		cmd_id=$cmd_id_response

	done

	sleep 5 # 每隔5秒检测一次
	# 获取当前时间（秒）
	#current_time=$(date +%s)
	# 计算时间差（秒）
	#elapsed_time=$((current_time - start_time))
	# 定义一分钟的秒数
	#one_minute=60
	# 比较时间差，如果超过一分钟则退出
	# if [ "$elapsed_time" -gt "$one_minute" ]; then
	# 	echo "脚本执行时间超过1分钟，正在退出..."
	# 	exit 0
	# fi

done
